/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package tal.drivechain.module.properties;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import tal.drivechain.parsing.Tokenizer;
import tal.drivechain.parsing.ParsingAid;
import tal.drivechain.module.xmlconfig.PropertyInfo;

/**
 * 
 * @author Main
 */
public class PropertiesManager {
    HashMap<String,Object> props = null;
    List<PropertyInfo> info;
    
    public PropertiesManager(List<PropertyInfo> info) {
        this.info = info;
        
    }

    public PropertyInfo getPropertyInfo(String name) throws IllegalArgumentException {
        for (PropertyInfo i : info)
            if (i.getName().equals(name))
                return i;
        throw new IllegalArgumentException("Property '" + name + "' doesn't exist.");
    }

    public List<PropertyInfo> getAllPropertyInfo() { return info; }
    
    public Object getProperty(String name) throws IllegalArgumentException {
        if (props!=null) {
            return props.get(name);
        } else throw new IllegalArgumentException("Trying to get a property data before parsing properties.");
    }
    
    public HashMap<String,Object> getAllProperties() { return props; }

    public boolean hasProperty(String name) {
        return props.containsKey(name);
    }
    
    public void parseProperties(String strProps) throws IllegalArgumentException {
        //tokenize
        if (props!=null) props.clear();
        String[] tokenProps = new Tokenizer(strProps, ';').getTokens();
        if (tokenProps.length==1)
            tokenProps = new Tokenizer(strProps, '^').getTokens();
        for (int i=0; i<tokenProps.length; i++) {
            tokenProps[i] = tokenProps[i].trim();
            if (tokenProps[i].endsWith(";")) tokenProps[i] = tokenProps[i].substring(0, tokenProps[i].length()-1);
        }
        
        props = new HashMap<String, Object>();
        PropertyInfo keyValueProperty = null;
        PropertyInfo openSequenceProperty = null;
        
        //check for keyvalue parser properties and open-sequence properties
        for (PropertyInfo propInfo : info) {
            if (propInfo.getParser() instanceof KeyValueParser)
                keyValueProperty = propInfo;
            else if (propInfo.getAttributes().get("open-sequence")!=null
                    && propInfo.getAttributes().get("open-sequence").equals("true"))
                openSequenceProperty = propInfo;
        }        
        
        
        //step through each property
        for (int i=0; i<tokenProps.length; i++) {
            if (tokenProps[i].trim().length()!=0) {
                String[] keyValuePair = new Tokenizer(tokenProps[i], ':').getTokens();
                for (int m=0; m<keyValuePair.length; m++) keyValuePair[m] = keyValuePair[m].trim();
               
                if (keyValuePair.length==2) { // can be any type of property (named)
                    //split the property by the first equal sign
                    //look if this property name appears in the list.
                    //if not it might be a pair property. the paired property
                    //may or may not be repeatable. keep count of paired and other named property occurences
                    PropertyInfo propInfo = null;
                    for (PropertyInfo prop : info) {
                        if (prop.getName().equals(keyValuePair[0])) {
                            propInfo = prop;
                            break;
                        }
                    }
                    //check if a match was found
                    if (propInfo!=null) {
                        try {
                            Object parsedProp = propInfo.getParser().parseProperty(keyValuePair[1]);
                            if (propInfo.isRepeatable()) {
                                // check if this is the first time this property was encountered.
                                if (!props.containsKey(keyValuePair[0])) {
                                    List<Object> propList = new ArrayList<Object>();
                                    propList.add(parsedProp);
                                    props.put(keyValuePair[0], propList);
                                } else {
                                    List<Object> propList = (List<Object>)props.get(keyValuePair[0]);
                                    propList.add(parsedProp);
                                }
                            } else {
                                if (props.containsKey(keyValuePair[0])) {
                                    throw new IllegalArgumentException("Found a repeated property which is non-repeatable (" + keyValuePair[0] + "):\n\t" + strProps);
                                } else {
                                    props.put(keyValuePair[0], parsedProp);
                                }
                            }
                        } catch (IllegalArgumentException ie) {
                            throw new IllegalArgumentException("In property " + propInfo.getName() + ": " + ie.getMessage());
                        }                        
                    } else { //propInfo==null
                        //if the module has a keyvalue property treat it as such
                        if (keyValueProperty!=null) {
                            // (has to be repeatable!)
                            try {
                                Object parsedProp = keyValueProperty.getParser().parseProperty(tokenProps[i]);
                                if (!props.containsKey(keyValueProperty.getName())) {
                                    List<Object> propList = new ArrayList<Object>();
                                    propList.add(parsedProp);
                                    props.put(keyValueProperty.getName(), propList);
                                } else {
                                    List<Object> propList = (List<Object>)props.get(keyValueProperty.getName());
                                    propList.add(parsedProp);
                                }                            
                            } catch (IllegalArgumentException ie) {
                                throw new IllegalArgumentException("In property " + keyValueProperty.getName() + ": " + ie.getMessage());
                            }
                        } else
                            throw new IllegalArgumentException("Unknown property name: \"" + keyValuePair[0] + "\"");
                    }
                } else if (keyValuePair.length==1) {
                    // the property must be an ordered property. these may not be repeatable unless an property exists
                    // with an attribute of name="open-sequence" value="true"
                    // lookup the current index and find an property that fits the index.
                    PropertyInfo propInfo = null;
                    for (PropertyInfo prop : info) {
                        if (prop.isOrdered() && prop.getOrderIndex()==i) {
                            propInfo = prop;
                            break;
                        }
                    }
                    if (propInfo==null) { // can only be part of an open-sequence otherwise error!
                        if (openSequenceProperty!=null) {
                            try {
                                Object parsedProperty = openSequenceProperty.getParser().parseProperty(keyValuePair[0]);
                                if (!props.containsKey(openSequenceProperty.getName())) {
                                    List<Object> propList = new ArrayList<Object>();
                                    propList.add(parsedProperty);
                                    props.put(openSequenceProperty.getName(), propList);
                                } else {
                                    List<Object> propList = (List<Object>)props.get(openSequenceProperty.getName());
                                    propList.add(parsedProperty);
                                }
                            } catch (IllegalArgumentException ie) {
                                throw new IllegalArgumentException("In property " + openSequenceProperty.getName() + ": " + ie.getMessage());
                            }
                        } else 
                            throw new IllegalArgumentException("Illegal unnamed property (#" + (i+1) + " " + keyValuePair[0] + "):\n\t" + strProps);
                    } else {
                        try {
                            Object parsedProperty = propInfo.getParser().parseProperty(keyValuePair[0]);
                            props.put(propInfo.getName(), parsedProperty);
                        } catch (IllegalArgumentException ie) {
                            throw new IllegalArgumentException("In property " + propInfo.getName() + ": " + ie.getMessage());
                        }                    
                    }
                } else if (keyValuePair.length>2) { //error
                    throw new IllegalArgumentException("Invalid property syntax: " + tokenProps[i] + " (more than one ':' sign)");
                } else throw new IllegalArgumentException("Fatal property parsing error: Tokenizer returned an empty array.");
            }
        }
        // in the end look that every property that doesn't have an optional attribute
        // is found in the list.
            
        for (PropertyInfo propInfo : info) {
            if (!props.containsKey(propInfo.getName())) {
                if (propInfo.isOptional()) {
                    String defaultValue = propInfo.getDefaultValueString();
                    if (defaultValue.equals("empty")) {
                        if (propInfo.getParser() instanceof KeyValueParser) {
                            props.put(propInfo.getName(), new ArrayList<Object[]>());
                        } else if (propInfo.isRepeatable())
                            props.put(propInfo.getName(), new ArrayList<Object>());
                    } else {
                        Object defaultVal = propInfo.getDefaultValue();
                        if (defaultVal!=null)
                            props.put(propInfo.getName(), defaultVal);
                    }
                } else {
                    throw new IllegalArgumentException("Required module property " + propInfo.getName() + " (" + propInfo.getParser().getPropertyType() + ") is not set:\n\t" + strProps);
                }
            } 
        }
    }

    public Object parseProp(String prop) {
        PropertyInfo OpenSequenceProperty = null;
        PropertyInfo keyValueProperty = null;
        PropertyInfo a = null;
        for (PropertyInfo propInfo : info) {
            if (propInfo.getParser() instanceof KeyValueParser)
                keyValueProperty = propInfo;
            else if (propInfo.getAttributes().get("open-sequence")!=null
                    && propInfo.getAttributes().get("open-sequence").equals("true"))
                OpenSequenceProperty = propInfo;
        }        
        
        prop = ParsingAid.parseEscapeCharacters(prop);
        String[] keyValuePair = new Tokenizer(prop, ':').getTokens();
        for (int i=0; i<keyValuePair.length; i++) keyValuePair[i] = keyValuePair[i].trim();
        
        if (keyValuePair.length>=2) {
            for (PropertyInfo propInfo : info) {
                if (keyValuePair[0].equals(propInfo.getName()))
                    a = propInfo;
            }
            if (a==null) {
                if (keyValueProperty!=null) {
                        return keyValueProperty.getParser().parseProperty(prop);
                }
                else throw new IllegalArgumentException("Unknown property name \"" + keyValuePair[0] + "\"");
            } else {
                return a.getParser().parseProperty(keyValuePair[1]);
            }
                
        } else {
            if (OpenSequenceProperty!=null) {
                return OpenSequenceProperty.getParser().parseProperty(prop);
            } else throw new IllegalArgumentException("No open sequence property found. Expected name: value (" + prop + ")");
        } 
    }
    
    public String parsePropName(String prop) throws IllegalArgumentException {
        PropertyInfo openSequenceProperty = null;
        PropertyInfo keyValueProperty = null;
        for (PropertyInfo propInfo : info) {
            if (propInfo.getParser() instanceof KeyValueParser)
                keyValueProperty = propInfo;
            else if (propInfo.getAttributes().get("open-sequence")!=null
                    && propInfo.getAttributes().get("open-sequence").equals("true"))
                openSequenceProperty = propInfo;
        }        
        
        prop = ParsingAid.parseEscapeCharacters(prop);
        String[] keyValuePair = new Tokenizer(prop, ':').getTokens();
        for (int i=0; i<keyValuePair.length; i++) keyValuePair[i] = keyValuePair[i].trim();
        
        if (keyValuePair.length>=2) {
            PropertyInfo found = null;
            for (PropertyInfo propInfo : info) {
                if (keyValuePair[0].equals(propInfo.getName()))
                    found = propInfo;
            }
            if (found!=null)
                return found.getName();
            else {
                if (keyValueProperty!=null)
                        return keyValueProperty.getName();
                else throw new IllegalArgumentException("Unknown property name \"" + keyValuePair[0] + "\"");
            }
                
        } else {
            if (openSequenceProperty!=null)
                return openSequenceProperty.getName();
            else throw new IllegalArgumentException("No open sequence property found. Expected name: value (" + prop + ")");
        } 
    }
    
    public int size() {
        return this.props.size();
    }
}
